package rand

import (
	"math/rand"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

// In [min,max]
func In(min int, max int) int {
	if min == max {
		return min
	}
	return min + Le(max-min)
}

// Lt LessThan
// [0,n)
func Lt(n int) int {
	if n == 0 {
		return 0
	}
	nn := n
	if n < 0 {
		nn = -nn
	}
	r := rand.Intn(nn)
	if n < 0 {
		r = -r
	}
	return r
}

// Le LessEqual
// [0,n]
func Le(n int) int {
	if n == 0 {
		return 0
	}
	nn := n
	if n < 0 {
		nn = -nn
	}
	r := rand.Intn(nn + 1)
	if n < 0 {
		r = -r
	}
	return r
}

//

type Weighter interface {
	Weight() int
}

type SelectDetail struct {
	Index int
	Total int
	Rand  int
}

type WeightSelector[T Weighter] func() (T, SelectDetail)

func MakeWeightSelector[T Weighter](ws []T) WeightSelector[T] {
	total := 0
	for _, w := range ws {
		if ww := w.Weight(); ww > 0 {
			total += ww
		}
	}
	return func() (T, SelectDetail) {
		if total > 0 {
			rnd := In(1, total)
			acc := 0
			for i, w := range ws {
				if ww := w.Weight(); ww > 0 {
					acc += ww
					if rnd <= acc {
						return w, SelectDetail{i, total, rnd}
					}
				}
			}
		}
		var t T
		return t, SelectDetail{-1, 0, 0}
	}
}

func WeightSelectDetail[T Weighter](ws []T) (T, SelectDetail) {
	return MakeWeightSelector(ws)()
}

func WeightSelect[T Weighter](ws []T) T {
	o, _ := MakeWeightSelector(ws)()
	return o
}

//

func Shuffle[T any](slice []T) {
	for i := len(slice) - 1; i > 0; i-- {
		j := Le(i)
		slice[i], slice[j] = slice[j], slice[i]
	}
}

func OneOf[T any](slice []T) T {
	return slice[Lt(len(slice))]
}

//

var (
	UpperChars   = []rune{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}
	LowerChars   = []rune{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}
	NumChars     = []rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}
	SpecialChars = []rune{'~', '!', '@', '#', '%', '^', '&', '*', '(', ')', '_', '+', '|', '{', '}', ':', '"', '<', '>', '?', '`', '-', '=', '\\', '[', ']', ';', '\'', ',', '.', '/'}
	ULNChars     = concatRunes(UpperChars, LowerChars, NumChars)
)

func concatRunes(charset ...[]rune) []rune {
	list := []rune{}
	for _, cs := range charset {
		list = append(list, cs...)
	}
	return list
}

func RandStr(length int, charset ...[]rune) string {
	if length <= 0 {
		return ""
	}
	list := concatRunes(charset...)
	if len(list) == 0 {
		list = ULNChars
	}
	s := make([]rune, length)
	for i := 0; i < length; i++ {
		s[i] = list[Lt(len(list))]
	}
	return string(s)
}
